#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
FTP下载同步工具
功能：将FTP服务器上的/spider3/upload/目录同步到本地E:/trae_project/spider3/upload/目录
同步逻辑：以FTP远端目录为基准，下载本地不存在的文件，保持目录结构一致
"""

import os
import ftplib
import datetime
import logging
from typing import List, Tuple

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("sync_ftp2local_" + datetime.datetime.now().strftime("%Y%m%d%H%M%S") + ".log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class FTPDownloadSync:
    """
    FTP下载同步类
    负责将FTP服务器上的文件同步到本地
    """
    
    def __init__(self, host: str, username: str, password: str):
        """
        初始化FTP连接信息
        
        Args:
            host: FTP服务器地址
            username: FTP用户名
            password: FTP密码
        """
        self.host = host
        self.username = username
        self.password = password
        self.ftp = None
        
        # 同步目录配置
        self.remote_base_dir = '/spider3/upload/'
        self.local_base_dir = 'E:/trae_project/spider3/upload1/'
        
        # 统计信息
        self.total_files = 0
        self.downloaded_files = 0
        self.skipped_files = 0
        self.errors = 0
        
    def connect(self) -> bool:
        """
        连接到FTP服务器
        
        Returns:
            bool: 连接是否成功
        """
        try:
            logger.info(f"正在连接FTP服务器: {self.host}")
            self.ftp = ftplib.FTP(self.host)
            self.ftp.login(self.username, self.password)
            logger.info("FTP服务器连接成功")
            # 设置为被动模式，更适合防火墙环境
            self.ftp.set_pasv(True)
            return True
        except Exception as e:
            logger.error(f"连接FTP服务器失败: {e}")
            return False
    
    def disconnect(self):
        """
        断开FTP连接
        """
        if self.ftp:
            try:
                self.ftp.quit()
                logger.info("FTP连接已断开")
            except Exception as e:
                logger.error(f"断开FTP连接时出错: {e}")
            finally:
                self.ftp = None
    
    def _ensure_local_dir_exists(self, local_dir: str):
        """
        确保本地目录存在，如果不存在则创建
        
        Args:
            local_dir: 本地目录路径
        """
        if not os.path.exists(local_dir):
            try:
                os.makedirs(local_dir)
                logger.info(f"创建本地目录: {local_dir}")
            except Exception as e:
                logger.error(f"创建本地目录失败: {local_dir}, 错误: {e}")
                raise
    
    def _get_ftp_file_size(self, file_path: str) -> int:
        """
        获取FTP文件大小
        
        Args:
            file_path: FTP文件路径
        
        Returns:
            int: 文件大小（字节），失败返回-1
        """
        try:
            # 使用SIZE命令获取文件大小
            size = self.ftp.size(file_path)
            return size
        except Exception as e:
            logger.warning(f"获取FTP文件大小失败: {file_path}, 错误: {e}")
            return -1
    
    def _get_ftp_dir_contents(self, ftp_path: str) -> Tuple[List[str], List[str]]:
        """
        获取FTP目录下的文件和子目录列表
        
        Args:
            ftp_path: FTP目录路径
        
        Returns:
            Tuple[List[str], List[str]]: (文件列表, 目录列表)
        """
        files = []
        dirs = []
        
        def _parse_line(line):
            # 解析LIST命令返回的行
            # 典型格式: -rw-r--r--   1 user group    12345 Jun 01 12:34 filename.txt
            # 或 drwxr-xr-x   1 user group       0 Jun 01 12:34 dirname/
            parts = line.split(maxsplit=8)
            if not parts:
                return
            
            # 第一部分的第一个字符表示类型: 'd' 表示目录, '-' 或其他表示文件
            if parts[0].startswith('d'):
                dirs.append(parts[-1])
            elif not parts[0].startswith('l'):  # 跳过符号链接
                files.append(parts[-1])
        
        try:
            self.ftp.cwd(ftp_path)
            self.ftp.retrlines('LIST', _parse_line)
        except Exception as e:
            logger.error(f"获取FTP目录内容失败: {ftp_path}, 错误: {e}")
            raise
        
        return files, dirs
    
    def _download_file(self, remote_file_path: str, local_file_path: str):
        """
        下载单个文件
        
        Args:
            remote_file_path: 远程文件路径
            local_file_path: 本地文件路径
        """
        try:
            with open(local_file_path, 'wb') as f:
                self.ftp.retrbinary(f'RETR {remote_file_path}', f.write)
            logger.info(f"文件下载成功: {remote_file_path} -> {local_file_path}")
            self.downloaded_files += 1
        except Exception as e:
            logger.error(f"文件下载失败: {remote_file_path}, 错误: {e}")
            self.errors += 1
            # 如果下载失败，删除不完整的文件
            if os.path.exists(local_file_path):
                try:
                    os.remove(local_file_path)
                except:
                    pass
    
    def sync_directory(self, remote_dir: str = None, local_dir: str = None):
        """
        同步目录
        
        Args:
            remote_dir: 远程目录路径
            local_dir: 本地目录路径
        """
        # 如果未指定，使用基础目录
        if remote_dir is None:
            remote_dir = self.remote_base_dir
        if local_dir is None:
            local_dir = self.local_base_dir
        
        # 确保本地目录存在
        self._ensure_local_dir_exists(local_dir)
        
        try:
            # 获取远程目录内容
            files, dirs = self._get_ftp_dir_contents(remote_dir)
            
            # 处理文件
            for file_name in files:
                self.total_files += 1
                remote_file_path = f"{remote_dir}{file_name}"
                local_file_path = os.path.join(local_dir, file_name)
                
                # 检查本地文件是否存在
                if not os.path.exists(local_file_path):
                    logger.info(f"发现新文件，开始下载: {remote_file_path}")
                    self._download_file(file_name, local_file_path)  # 注意：RETR命令使用相对路径
                else:
                    # 获取本地文件大小
                    local_size = os.path.getsize(local_file_path)
                    # 获取FTP文件大小
                    ftp_size = self._get_ftp_file_size(file_name)
                    
                    if ftp_size > 0 and local_size != ftp_size:
                        logger.info(f"文件大小不同，覆盖本地文件: {remote_file_path}")
                        logger.info(f"本地文件大小: {local_size} 字节, FTP文件大小: {ftp_size} 字节")
                        self._download_file(file_name, local_file_path)
                    else:
                        logger.debug(f"文件已存在且大小相同，跳过: {local_file_path}")
                        self.skipped_files += 1
            
            # 递归处理子目录
            for dir_name in dirs:
                # 过滤掉特殊目录名，避免死循环
                if dir_name in ['.', '..']:
                    continue
                
                new_remote_dir = f"{remote_dir}{dir_name}/"
                new_local_dir = os.path.join(local_dir, dir_name)
                logger.info(f"开始同步子目录: {new_remote_dir}")
                self.sync_directory(new_remote_dir, new_local_dir)
                # 返回到上级目录
                self.ftp.cwd('..')
                
        except Exception as e:
            logger.error(f"同步目录失败: {remote_dir}, 错误: {e}")
            self.errors += 1
    
    def run_sync(self):
        """
        执行完整的同步过程
        """
        start_time = datetime.datetime.now()
        logger.info("开始FTP同步任务")
        logger.info(f"远程基础目录: {self.remote_base_dir}")
        logger.info(f"本地基础目录: {self.local_base_dir}")
        
        try:
            # 连接FTP服务器
            if not self.connect():
                logger.error("FTP连接失败，同步任务终止")
                return False
            
            # 开始同步
            self.sync_directory()
            
            # 输出统计信息
            end_time = datetime.datetime.now()
            duration = (end_time - start_time).total_seconds()
            
            logger.info("\n===== 同步完成 =====")
            logger.info(f"总文件数: {self.total_files}")
            logger.info(f"新下载文件数: {self.downloaded_files}")
            logger.info(f"跳过文件数: {self.skipped_files}")
            logger.info(f"错误数: {self.errors}")
            logger.info(f"同步耗时: {duration:.2f} 秒")
            logger.info("====================")
            
            return self.errors == 0
            
        except Exception as e:
            logger.error(f"同步过程中发生未预期的错误: {e}")
            return False
        finally:
            # 确保断开连接
            self.disconnect()


def main():
    """
    主函数
    """
    # FTP服务器配置
    FTP_HOST = '127.0.0.0'
    FTP_USER = 'pdf_of'
    FTP_PASSWORD = '123456'
    
    # 创建同步器实例并运行
    sync = FTPDownloadSync(FTP_HOST, FTP_USER, FTP_PASSWORD)
    success = sync.run_sync()
    
    # 根据同步结果设置退出码
    exit_code = 0 if success else 1
    exit(exit_code)


if __name__ == "__main__":
    main()